﻿using GSPatEditor.Pat;
using LibBinary.DataSource;
using LibBinary.FileData;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GSPatEditor.Serialization
{
    static class PatFileReader
    {
        private static void Main()
        {
            var pat = ReadPatFile(@"E:\Games\[game]GRIEFSYNDROME\griefsyndrome\gs00\data\actor\homura\homura.pat");
            UI.AnimationPreview.ShowPreviewWindow(pat.Animations[26], pat.Palettes[1]);
        }

        private class ResourceList
        {
            private Resources Resource;
            private string[] FileNames;
            private Image[] ImageList;

            public ResourceList(Resources res, string[] filename)
            {
                Resource = res;
                FileNames = filename;
                ImageList = new Image[filename.Length];
            }

            public Image GetImage(int index)
            {
                var ret = ImageList[index];
                if (ret == null)
                {
                    ret = Resource.GetImageByFileName(FileNames[index]);
                    ImageList[index] = ret;
                    return ret;
                }
                return ret;
            }
        }

        public static PatFile ReadPatFile(string path)
        {
            AbstractFileData data;
            using (var stream = File.OpenRead(path))
            {
                var reader = new BinaryFileSource(stream);
                data = PatFileFormatProvider.Format.Read(reader, null);
            }

            var directory = Path.GetDirectoryName(path);
            var ret = new PatFile();

            ret.Palettes = Directory.GetFiles(directory, "*.pal")
                .Select(file => Palette.CreateFromFile(file))
                .Where(pal => pal != null).ToArray();

            var res = Resources.CreateSingleDirectoryResources(directory);
            ret.Resources = res;

            var imageListData = data.As<StructuredData>().Data[1].As<StructuredData>().Data[1].As<ArrayData>().Data;
            var imagePathList = imageListData.Select(d => d.As<StringData>().Data).ToArray();
            var images = new ResourceList(res, imagePathList);

            //TODO animations
            var animationListData = data.As<StructuredData>().Data[2].As<StructuredData>().Data[1].As<ArrayData>().Data;
            var animationList = new List<Animation>();

            List<int> animationIndexList = new List<int>();
            List<Frame> frameList = new List<Frame>();
            List<KeyFrameInfo> keyFrameList = new List<KeyFrameInfo>();

            for (int i = 0; i < animationListData.Length; ++i)
            {
                var animationStruct = animationListData[i].As<StructuredData>();
                var animationID = animationStruct.Data[0].As<FieldData>().Data.As<PrimitiveData<int>>().Data;

                if (animationID == -1)
                {
                    //skip Clone
                    continue;
                }

                var normalAnimationInfoStruct = animationStruct.Data[3].As<ConditionalData>().Data.As<StructuredData>();
                if (animationID == -2)
                {
                    var key = ReadKeyFrame(normalAnimationInfoStruct);
                    key.FrameIndex = frameList.Count;
                    keyFrameList.Add(key);
                }
                else
                {
                    //new animation
                    if (animationIndexList.Count != 0)
                    {
                        //not the first, we need to finish last animation
                        var animation = new Animation();
                        animation.FrameList.AddRange(frameList);
                        animation.KeyFrameIndexList.AddRange(keyFrameList);

                        frameList.Clear();
                        keyFrameList.Clear();

                        animationList.Add(animation);
                    }

                    //push current index
                    animationIndexList.Add(animationID);

                    //read key frame
                    var key = ReadKeyFrame(normalAnimationInfoStruct);
                    keyFrameList.Add(key);
                }

                //read frames
                {
                    var frameListData = normalAnimationInfoStruct.Data[3].As<StructuredData>().Data[1].As<ArrayData>().Data;
                    foreach (var frameData in frameListData)
                    {
                        var frame = ReadFrame(frameData.As<StructuredData>(), images);
                        frameList.Add(frame);
                    }
                }
            }

            //finish last animation
            if (animationIndexList.Count != 0)
            {
                var animation = new Animation();
                animation.FrameList.AddRange(frameList);
                animation.KeyFrameIndexList.AddRange(keyFrameList);
                animationList.Add(animation);
            }

            ret.Animations = animationList.ToArray();

            ret.AnimationIndexMap = new Dictionary<int, int>();
            for (int i = 0; i < animationIndexList.Count; ++i)
            {
                ret.AnimationIndexMap.Add(animationIndexList[i], i);
            }

            return ret;
        }

        private static KeyFrameInfo ReadKeyFrame(StructuredData data)
        {
            var ret = new KeyFrameInfo();

            ret.AttackLevel = (AttackLevel)data.Data[0].FieldAs<PrimitiveData<short>>().Data;
            ret.CancelLevel = (CancelLevel)data.Data[1].FieldAs<PrimitiveData<short>>().Data;
            ret.IsLoop = data.Data[2].FieldAs<PrimitiveData<bool>>().Data;

            return ret;
        }

        private static Frame ReadFrame(StructuredData data, ResourceList images)
        {
            var ret = new Frame();

            ret.Image = new ImageRegion
            {
                Image = images.GetImage(data.Data[0].FieldAs<PrimitiveData<int>>().Data),
                ImageOffset = new Vector2D
                {
                    X = data.Data[1].FieldAs<PrimitiveData<short>>().Data,
                    Y = data.Data[2].FieldAs<PrimitiveData<short>>().Data,
                },
                DisplaySize = new Vector2D
                {
                    X = data.Data[3].FieldAs<PrimitiveData<short>>().Data,
                    Y = data.Data[4].FieldAs<PrimitiveData<short>>().Data,
                },
            };
            ret.Origin = new Vector2D
            {
                X = data.Data[5].FieldAs<PrimitiveData<short>>().Data,
                Y = data.Data[6].FieldAs<PrimitiveData<short>>().Data,
            };
            ret.FrameCount = data.Data[7].FieldAs<PrimitiveData<short>>().Data;
            ret.ImageTransformation = ReadTransformation(data.Data[8].FieldAs<StructuredData>());
            ret.Damage = ReadDamage(data);
            ret.StateFlag = (FrameStateFlag)data.Data[30].FieldAs<PrimitiveData<int>>().Data;
            ret.AttackFlag = (FrameAttackFlag)data.Data[31].FieldAs<PrimitiveData<int>>().Data;
            ret.PhysicsBox = ReadPhysics(data.Data[32].FieldAs<StructuredData>());
            ret.HitBoxes.AddRange(ReadBoxes(data.Data[33].FieldAs<StructuredData>()));
            ret.AttackBoxes.AddRange(ReadBoxes(data.Data[34].FieldAs<StructuredData>()));
            {
                var pointsData = data.Data[35].FieldAs<ArrayData>().Data;
                ret.Points[0] = ReadPoint(pointsData[0].As<StructuredData>());
                ret.Points[1] = ReadPoint(pointsData[1].As<StructuredData>());
                ret.Points[2] = ReadPoint(pointsData[2].As<StructuredData>());
            }

            return ret;
        }

        private static FrameImageTransformation ReadTransformation(StructuredData data)
        {
            if (data.Data[0].As<PrimitiveData<byte>>().Data != 2)
            {
                return null;
            }

            var structData = data.Data[1].As<ConditionalData>().Data.As<StructuredData>();
            var ret = new FrameImageTransformation();
            ret.AlphaBlend = (AlphaBlendOption)structData.Data[0].FieldAs<PrimitiveData<short>>().Data;
            ret.Scale = new Vector2D
            {
                X = structData.Data[5].FieldAs<PrimitiveData<short>>().Data,
                Y = structData.Data[6].FieldAs<PrimitiveData<short>>().Data,
            };
            ret.Rotate = structData.Data[9].FieldAs<PrimitiveData<short>>().Data;
            //TODO support other fields
            return ret;
        }

        private static FrameDamangeInfo ReadDamage(StructuredData frameData)
        {
            var ret = new FrameDamangeInfo();
            ret.AttackRate = frameData.Data[9].FieldAs<PrimitiveData<short>>().Data;
            ret.HitStopOther = frameData.Data[10].FieldAs<PrimitiveData<short>>().Data;
            ret.HitStopSelf = frameData.Data[11].FieldAs<PrimitiveData<short>>().Data;
            ret.Bind = frameData.Data[12].FieldAs<PrimitiveData<short>>().Data;
            ret.SoundEffect = frameData.Data[26].FieldAs<PrimitiveData<short>>().Data;
            ret.AttackType = frameData.Data[27].FieldAs<PrimitiveData<short>>().Data;
            ret.HitVX = frameData.Data[36].FieldAs<PrimitiveData<short>>().Data;
            ret.HitVY = frameData.Data[37].FieldAs<PrimitiveData<short>>().Data;
            ret.HitG = frameData.Data[38].FieldAs<PrimitiveData<short>>().Data;
            return ret;
        }

        private static AxisAlignedBox ReadPhysics(StructuredData data)
        {
            if (!data.Data[0].As<PrimitiveData<bool>>().Data)
            {
                return null;
            }

            var structData = data.Data[1].As<ConditionalData>().Data.As<StructuredData>();
            var ret = new AxisAlignedBox();
            ret.Position1 = new Vector2D
            {
                X = structData.Data[0].FieldAs<PrimitiveData<int>>().Data,
                Y = structData.Data[1].FieldAs<PrimitiveData<int>>().Data,
            };
            ret.Position2 = new Vector2D
            {
                X = structData.Data[2].FieldAs<PrimitiveData<int>>().Data,
                Y = structData.Data[3].FieldAs<PrimitiveData<int>>().Data,
            };

            return ret;
        }

        private static Box[] ReadBoxes(StructuredData data)
        {
            int count = data.Data[0].As<PrimitiveData<byte>>().Data;
            Box[] ret = new Box[count];

            var boxDataArray = data.Data[1].As<ArrayData>().Data;
            for (int i = 0; i < count; ++i)
            {
                var boxData = boxDataArray[i].As<StructuredData>();
                ret[i] = new Box
                {
                    Position1 = new Vector2D
                    {
                        X = boxData.Data[0].FieldAs<PrimitiveData<int>>().Data,
                        Y = boxData.Data[1].FieldAs<PrimitiveData<int>>().Data,
                    },
                    Position2 = new Vector2D
                    {
                        X = boxData.Data[2].FieldAs<PrimitiveData<int>>().Data,
                        Y = boxData.Data[3].FieldAs<PrimitiveData<int>>().Data,
                    },
                    Rotation = boxData.Data[4].FieldAs<PrimitiveData<short>>().Data,
                };
            }

            return ret;
        }

        private static FramePointReference ReadPoint(StructuredData data)
        {
            return new FramePointReference
            {
                Position = new Vector2D
                {
                    X = data.Data[0].FieldAs<PrimitiveData<short>>().Data,
                    Y = data.Data[2].FieldAs<PrimitiveData<short>>().Data,
                },
            };
        }
    }
}
